﻿

namespace BackupLib
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Security.AccessControl;
    using System.IO;
    using Microsoft.Win32.SafeHandles;
    using Trinet.Core.IO.Ntfs;
    using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

    public class APIWrapper
    {
        private const int MAX_PATH= 260;
        
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct WIN32_FIND_DATAW
        {
            public uint dwFileAttributes;
            public FILETIME ftCreationTime;
            public FILETIME ftLastAccessTime;
            public FILETIME ftLastWriteTime;
            public uint nFileSizeHigh;
            public uint nFileSizeLow;
            public uint dwReserved0;
            public uint dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 520)]
            public string cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x1c)]
            public string cAlternateFileName;
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);
 
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool FindNextFileW(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll")]
        private static extern bool FindClose(IntPtr hFindFile);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
        
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);

        public static void CreateHardLink(string sourcePath, string destPath)
        {
            destPath = FormatPathUnicode(destPath);
            sourcePath = FormatPathUnicode(sourcePath);
            if (!CreateHardLink(destPath, sourcePath, IntPtr.Zero))
            {
                Win32Exception ex = new Win32Exception();
                throw ex;
            }
        }
        public static void CopyFile(bool replace, string sourcePath, string destPath)
        {
            destPath = FormatPathUnicode(destPath);
            sourcePath = FormatPathUnicode(sourcePath);
            if (!CopyFile(sourcePath, destPath, !replace))
            {
                Win32Exception ex = new Win32Exception();
                throw ex;
            }
        }


        public static void CreateDirectory(string path)
        {
            if (Path.GetPathRoot(path) == path)
            {
                return;
            }
            if (GetFile(PathUtil.GetParentDirectory(path))==null)
            {
                CreateDirectory(PathUtil.GetParentDirectory(path));
            }
            path = FormatPathUnicode(path);
            if (!CreateDirectory(path, IntPtr.Zero))
            {
                Win32Exception ex = new Win32Exception();
                if (ex.NativeErrorCode != 183)
                {
                    throw ex;
                }
            }
        }

        public static void SetFileAttributes(string path, FileAttributes attributes)
        {
            if (!SetFileAttributes(FormatPathUnicode(path), (uint) attributes))
            {
                Win32Exception ex = new Win32Exception();
                throw ex;
            }
        }

        private static string FormatPathNormal(string path)
        {
            if (path.IndexOf(@"\\?\UNC\") == 0)
            {
                return (@"\\" + path.Substring(8, path.Length - 8));
            }
            if (path.IndexOf(@"\\?\") == 0)
            {
                return path.Substring(4, path.Length - 4);
            }
            return path;
        }

        private static string FormatPathUnicode(string path)
        {
            if (path.IndexOf(@"\\?\") == 0)
            {
                return path;
            }
            if (path.IndexOf(@"\\") == 0)
            {
                return (@"\\?\UNC\" + path.Substring(2, path.Length - 2));
            }
            return (@"\\?\" + path);
        }

        public static FileAttrib GetFile(string path)
        {
            path = path.Trim(Path.DirectorySeparatorChar);
            WIN32_FIND_DATAW win_find_dataw;
            IntPtr hFindFile = FindFirstFileW(FormatPathUnicode(path), out win_find_dataw);
            if (hFindFile.ToInt32() == -1)
            {
                return null;
            }
            FindClose(hFindFile);
            return new FileAttrib(path, win_find_dataw.dwFileAttributes);
        }

        public static FileAttrib[] GetFiles(string source, string searchPattern)
        {
            WIN32_FIND_DATAW win_find_dataw;
            string str;
            List<FileAttrib> list = new List<FileAttrib>();
            if (!source.Substring(source.Length - 1, 1).Equals(@"\"))
            {
                source += @"\";
            }
            string sourceNormal=source;
            source = FormatPathUnicode(source);
            str = source + searchPattern;
            
            IntPtr hFindFile = FindFirstFileW(str, out win_find_dataw);
            if (hFindFile.ToInt32() == -1)
            {
                Win32Exception ex = new Win32Exception();
                List<int> ignores=new List<int>(new int[]{2,5});
                if (!ignores.Contains(ex.NativeErrorCode ))
                {
                    throw ex;
                }
            }
            else
            {
                if (!win_find_dataw.cFileName.Equals(".") && !win_find_dataw.cFileName.Equals(".."))
                {
                    FileAttrib fa = new FileAttrib(sourceNormal + win_find_dataw.cFileName, win_find_dataw.dwFileAttributes);
                    fa.CreationTime = FileTime2DateTime(win_find_dataw.ftCreationTime);
                    fa.LastWrite = FileTime2DateTime(win_find_dataw.ftLastWriteTime);
                    fa.LastRead = FileTime2DateTime(win_find_dataw.ftLastAccessTime);
                    fa.Length = HighLow2Long(win_find_dataw.nFileSizeHigh, win_find_dataw.nFileSizeLow);
                    
                    list.Add(fa);
                }

                while (FindNextFileW(hFindFile, out win_find_dataw))
                {
                    if (!win_find_dataw.cFileName.Equals(".") && !win_find_dataw.cFileName.Equals(".."))
                    {
                        FileAttrib fa = new FileAttrib(sourceNormal + win_find_dataw.cFileName, win_find_dataw.dwFileAttributes);
                        fa.CreationTime = FileTime2DateTime(win_find_dataw.ftCreationTime);
                        fa.LastWrite = FileTime2DateTime(win_find_dataw.ftLastWriteTime);
                        fa.LastRead = FileTime2DateTime(win_find_dataw.ftLastAccessTime);
                        fa.Length = HighLow2Long(win_find_dataw.nFileSizeHigh, win_find_dataw.nFileSizeLow);
                        list.Add(fa);
                    }
                }
            }
            FindClose(hFindFile);
            /*if (list.Count <= 0)
            {
                return null;
            }*/

            return list.ToArray();
        }

        public static DateTime FileTime2DateTime(FILETIME ft)
        {
            long fileTime = (((long)ft.dwHighDateTime) << 0x20) + ft.dwLowDateTime;
            //long ft2 = ((long)ft.dwHighDateTime) << 0x20;
            if (ft.dwLowDateTime < 0)
            {
                fileTime += 0x100000000L;
            }
            return DateTime.FromFileTime(fileTime);
        }


        public static long HighLow2Long(long high, long low)
        {
            if (low < 0L)
            {
                return ((high << 0x20) - low);
            }
            return ((high << 0x20) + low);
        }

        public static void CopyACL(string source, string destination)
        {
            if (source.Length > MAX_PATH || destination.Length > MAX_PATH)
            {
                throw new NotImplementedException("Copy of ACL for long paths");
            }

            FileSecurity sourceACL = File.GetAccessControl(source, AccessControlSections.All);
            FileSecurity destinationACL = File.GetAccessControl(destination, AccessControlSections.All);

            destinationACL.SetSecurityDescriptorBinaryForm(sourceACL.GetSecurityDescriptorBinaryForm());
            File.SetAccessControl(destination, destinationACL);
        }

        public static Dictionary<string, byte[]> GetADS(string path)
        {
            var dataStreams = FileSystem.ListAlternateDataStreams(path);
            Dictionary<string, byte[]> result = new Dictionary<string, byte[]>();
            foreach (AlternateDataStreamInfo dataStream in dataStreams)
            {
                byte[] buffer;
                FileStream fileStream = dataStream.OpenRead();
                try
                {
                    int length = (int)fileStream.Length;  // get file length
                    buffer = new byte[length];            // create buffer
                    int count;                            // actual number of bytes read
                    int sum = 0;                          // total number of bytes read

                    // read until Read method returns 0 (end of the stream has been reached)
                    while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
                        sum += count;  // sum is a buffer offset for next reading
                }
                finally
                {
                    fileStream.Close();
                }
                result.Add(dataStream.Name,buffer);
            }
            return result;
        }

        public static void RemoveADS(string path)
        {
            var dataStreams =FileSystem.ListAlternateDataStreams(path);
            foreach (AlternateDataStreamInfo dataStream in dataStreams)
            {
                FileSystem.DeleteAlternateDataStream(path, dataStream.Name);
            }
        }
    }
}
